﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;

namespace XCSerialPort
{
    public class SerialPort_CH341_SensorManager : ISerialPortManager
    {
        private SerialPort _serialPort;
        public ulong deviceID;
        public bool _8Addr8Data;
        public bool _16Addr8Data;
        public bool _16Addr16Data;

        public event Action<string> SendSensor_i2cClick;

        public SerialPort_CH341_SensorManager() { }

        public SerialPort_CH341_SensorManager(ConfigType config)
        {
            _serialPort = new SerialPort
            {
                PortName = config.PortName,
                BaudRate = config.BaudRate,
                DataBits = config.DataBits,
                StopBits = (System.IO.Ports.StopBits)config.StopBits, // 设置停止位
                Parity = (System.IO.Ports.Parity)config.Parity, // 设置奇偶校验
                ReadTimeout = 500,
                WriteTimeout = 500
            };
        }

        public void SetDeviceConfig(ulong deviceId, bool addr8Data, bool addr16Data8, bool addr16Data16)
        {
            deviceID = deviceId;
            _8Addr8Data = addr8Data;
            _16Addr8Data = addr16Data8;
            _16Addr16Data = addr16Data16;
        }

        public void Close(out string msg)
        {
            try
            {
                if (_serialPort.IsOpen)
                {
                    _serialPort.Close();
                    msg = "串口关闭成功";
                }
                else
                {
                    msg = "串口已关闭";
                }
            }
            catch (Exception ex)
            {
                msg = "关闭串口失败: " + ex.Message;
            }
        }

        public string[] GetSerialPorts(out string msg)
        {
            try
            {
                var ports = SerialPort.GetPortNames();
                msg = "成功获取可用串口";
                return ports;
            }
            catch (Exception ex)
            {
                msg = $"获取串口失败: {ex.Message}";
                return Array.Empty<string>();
            }
        }

        public bool Open(out string msg)
        {
            try
            {
                if (!_serialPort.IsOpen)
                {
                    _serialPort.Open();
                }
                msg = "串口打开成功";
                return true;
            }
            catch (Exception ex)
            {
                msg = "打开串口失败: " + ex.Message;
                return false;
            }
        }

        private void I2C_Switch_Master()
        {
            var recdata = ReadRegister(0x31, 0x8050004e).ToString("X2");
            ulong value = Convert.ToUInt32(recdata, 16) | 0x10;
            WriteRegister(0x21, 0x8050004e, value);
        }

        public long ReadRegister(byte a, ulong addr)
        {
            uint DATA = 0;
            SendRegDataARM(0x1, 0x82000000, 0);
            ReadRegDataARM(a, (uint)addr, ref DATA);
            return DATA;
        }

        public void WriteRegister(byte a, ulong addr, ulong value)
        {
            SendRegDataARM(0x1, 0x82000000, 0);
            value = (value & 0xff) << ((int)(addr & 0x3) * 8);
            SendRegDataARM(a, (uint)addr, (uint)value);
        }

        public byte SendRegDataARM(byte a, uint ADDR, uint DATA)
        {
            byte[] sendbuf = new byte[9];
            sendbuf[0] = a;
            Buffer.BlockCopy(BitConverter.GetBytes(ADDR), 0, sendbuf, 1, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(DATA), 0, sendbuf, 5, 4);

            _serialPort.Write(sendbuf, 0, 9);

            byte value = 0x00;
            for (int i = 0; i < 100; i++)
            {
                if (_serialPort.BytesToRead > 0)
                {
                    int n = Math.Min(8, _serialPort.BytesToRead);
                    byte[] buf = new byte[n];
                    _serialPort.Read(buf, 0, n);
                    value = buf[0];
                    break;
                }
                Thread.Sleep(10);
            }

            return value;
        }

        public byte ReadRegDataARM(byte a, uint ADDR, ref uint DATA)
        {
            byte[] readbuf = new byte[15];
            byte[] buf = new byte[10];
            readbuf[0] = a;
            Buffer.BlockCopy(BitConverter.GetBytes(ADDR), 0, readbuf, 1, 4);

            _serialPort.Write(readbuf, 0, 5);

            int recv_cnt = 0;
            for (int i = 0; i < 100; i++)
            {
                if (_serialPort.BytesToRead > 0)
                {
                    int n = Math.Min(8, _serialPort.BytesToRead);
                    byte[] tempBuf = new byte[n];
                    _serialPort.Read(tempBuf, 0, n);
                    Buffer.BlockCopy(tempBuf, 0, readbuf, recv_cnt, n);
                    recv_cnt += n;

                    if (recv_cnt >= 5)
                    {
                        byte value = readbuf[0];
                        if (value == 0x11)
                        {
                            DATA = BitConverter.ToUInt32(readbuf, 1);
                        }
                        else
                        {
                            DATA = readbuf[1 + (3 - (ADDR & 0x3))];
                        }
                        break;
                    }
                }
                Thread.Sleep(10);
            }

            if (recv_cnt < 5)
            {
                SendSensor_i2cClick?.Invoke($"fail in send: {ADDR:X2}\r\n");
            }
            else
            {
                if (a != 0x1)
                {
                    SendSensor_i2cClick?.Invoke($"R:0x{ADDR:X8} 0x{DATA:X2}");
                }
            }

            return readbuf[0];
        }

        private void CommRecvBufClear()
        {
            byte[] buf = new byte[9];
            while (_serialPort.BytesToRead > 0)
            {
                int n = Math.Min(8, _serialPort.BytesToRead);
                _serialPort.Read(buf, 0, n);
            }
        }

        private void switch_pin_to_sensor()
        {
            uint reg_addr = 0x8050004d;
            uint recdata = 0;
            ReadRegData(0x31, reg_addr, ref recdata);
            recdata &= ~0x00000002U;
            int byte_sel = (int)(reg_addr & 0x3) * 8;
            uint senddata = (recdata & 0xff) << byte_sel;
            SendRegDataARM(0x21, reg_addr, senddata);
        }
        public byte ReadRegData(byte a, uint ADDR, ref uint DATA)
        {
            byte[] readbuf = new byte[15];
            byte[] buf = new byte[10];
            byte value = 0x00;
            int recv_cnt = 0;
            uint byte_sel = ADDR & 0x3;

            // TXD0 ADDRESS
            readbuf[0] = a; // 命令
            readbuf[1] = (byte)((ADDR >> 0) & 0xff); // 地址低字节
            readbuf[2] = (byte)((ADDR >> 8) & 0xff); // 地址中低字节
            readbuf[3] = (byte)((ADDR >> 16) & 0xff); // 地址中高字节
            readbuf[4] = (byte)((ADDR >> 24) & 0xff); // 地址高字节

            if (_serialPort.IsOpen)
            {
                CommRecvBufClear(); // 清空接收缓冲区
                _serialPort.Write(readbuf, 0, 5); // 发送数据

                DATA = 0; // 初始化数据

                for (int i = 0; i < 100; i++) // 尝试读取数据
                {
                    if (_serialPort.BytesToRead > 0)
                    {
                        int n = _serialPort.BytesToRead; // 获取字节数
                        if (n > 8) n = 8; // 限制读取的字节数
                        _serialPort.Read(buf, 0, n); // 读取数据

                        if ((recv_cnt + n) >= 12)
                        {
                            n = 12 - recv_cnt; // 确保不越界
                        }

                        Array.Copy(buf, 0, readbuf, recv_cnt, n); // 将读取的数据拷贝到 readbuf
                        recv_cnt += n;

                        if (recv_cnt >= 5) // 确保已经接收到足够的数据
                        {
                            value = readbuf[0]; // 响应字节

                            // 根据响应字节处理数据
                            if (readbuf[0] == 0x11)
                            {
                                DATA = (uint)((readbuf[1] << 24) + (readbuf[2] << 16) + (readbuf[3] << 8) + (readbuf[4] << 0));
                            }
                            else
                            {
                                switch (byte_sel)
                                {
                                    case 0:
                                        DATA = readbuf[4]; // 读取第4个字节
                                        break;
                                    case 1:
                                        DATA = readbuf[3]; // 读取第3个字节
                                        break;
                                    case 2:
                                        DATA = readbuf[2]; // 读取第2个字节
                                        break;
                                    case 3:
                                        DATA = readbuf[1]; // 读取第1个字节
                                        break;
                                    default:
                                        DATA = 0x0; // 默认值
                                        break;
                                }
                            }
                            break; // 跳出循环
                        }
                    }
                    else
                    {
                        Thread.Sleep(10); // 等待并重试
                    }
                }
            }
            else
            {
                SendSensor_i2cClick?.Invoke("串口关闭！"); // 串口未打开
            }
            return value; // 返回响应字节
        }

        public List<ulong> ReadRegister(ulong addr, out string msg, int length = 1, int timeoutMilliseconds = 150)
        {
            msg = "成功读取寄存器";
            List<ulong> values = new List<ulong>();

            for (int i = 0; i < length; i++)
            {
                try
                {
                    Thread.Sleep(30);

                    I2C_Switch_Master();
                    switch_pin_to_sensor();
                    i2c_writereg(0x03, (uint)deviceID);

                    ulong val = 0;

                    if (_8Addr8Data)
                        val = hw_i2c_read((uint)addr);
                    else if (_16Addr8Data)
                        val = hw_i2c_read16((uint)addr);
                    else if (_16Addr16Data)
                        val = hw_i2c_16read16((uint)addr);

                    values.Add(val);

                    SendSensor_i2cClick?.Invoke($"R:0x{addr:X4} 0x{val:X2}");
                }
                finally
                {
                    I2C_Switch_Slaver();
                }
            }

            return values;
        }

        public void WriteRegister(ulong addr, List<ulong> value, out string msg, int length = 1, int timeoutMilliseconds = 150)
        {
            msg = "成功写入寄存器";

            for (int i = 0; i < length; i++)
            {
                try
                {
                    Thread.Sleep(30);
                    I2C_Switch_Master();
                    switch_pin_to_sensor();
                    i2c_writereg(0x03, (uint)deviceID);

                    if (_8Addr8Data)
                        hw_i2c_write((uint)addr, (uint)value[i]);
                    else if (_16Addr8Data)
                        hw_i2c_write16((uint)addr, (uint)value[i]);
                    else if (_16Addr16Data)
                        hw_i2c_16write16((uint)addr, (uint)value[i]);

                    SendSensor_i2cClick?.Invoke($"W:0x{addr:X4} 0x{value[i]:X2}");
                }
                finally
                {
                    I2C_Switch_Slaver();
                }
            }
        }

        private void hw_i2c_write(uint addr, uint val)
        {
            i2c_writereg(addr, val);
        }

        private void hw_i2c_write16(uint addr, uint val)
        {
            i2c_writereg(addr, val);
        }

        private void hw_i2c_16write16(uint addr, uint val)
        {
            i2c_writereg(addr, val & 0xFF);
            i2c_writereg(addr + 1, (val >> 8) & 0xFF);
        }

        private void i2c_writereg(uint addr, uint val)
        {
            isp_write(0x80250000 + addr, val);
        }

        private uint hw_i2c_read(uint addr)
        {
            uint val;
            I2C_RDY();
            i2c_writereg(0x05, addr);
            i2c_writereg(0x04, 0x33);
            Thread.Sleep(2);
            I2C_WaitReady();
            i2c_writereg(0x04, 0xf9);
            Thread.Sleep(2);
            I2C_WaitReady();
            val = i2c_readreg(0x07);
            return val;
        }

        private uint hw_i2c_read16(uint addr)
        {
            uint val;
            I2C_RDY();
            i2c_writereg(0x05, (addr >> 8) & 0xff);
            i2c_writereg(0x06, addr & 0xff);
            i2c_writereg(0x04, 0x37);
            I2C_WaitReady();
            i2c_writereg(0x04, 0xf9);
            I2C_WaitReady();
            val = i2c_readreg(0x07);
            return val;
        }

        private uint hw_i2c_16read16(uint addr)
        {
            uint val1, val2;
            i2c_writereg(0x05, (addr >> 8) & 0xff);
            i2c_writereg(0x06, addr & 0xff);
            i2c_writereg(0x04, 0x17);
            Thread.Sleep(10);
            I2C_WaitReady();
            i2c_writereg(0x04, 0x59);
            Thread.Sleep(10);
            I2C_WaitReady();
            val1 = i2c_readreg(0x07);
            i2c_writereg(0x04, 0xa8);
            val2 = i2c_readreg(0x07);
            Thread.Sleep(10);
            I2C_WaitReady();
            return (val1 << 8) + val2;
        }

        private void I2C_WaitReady()
        {
            UInt16 i;
            uint temp;
            uint status;
            uint I2C_addr_base = 0x80250000;
            if ((i2c_readreg(0x00) & 0x80) == 0x80)
            {
                status = i2c_readreg(0x08);
                status = status & (uint)(~0x30U);
                i2c_writereg(0x08, status);
                for (i = 0; i < 10; i++)
                {
                    temp = (read(I2C_addr_base + 0x08) >> 4) & 3;
                    if (temp == 3)
                    {
                        break;
                    }
                }
                if (i == 10)
                {
                    status = i2c_readreg(0x08);
                    status = status | 0x30U;
                    i2c_writereg(0x08, status);
                    // Handle timeout logic here
                }
            }
            else
            {
                I2C_RDY();
            }
        }

        private void I2C_RDY()
        {
            var dt0 = DateTime.Now;
            while ((read(0x80250000 + 0x08) & 0x01) != 0x0)
            {
                if (DateTime.Now.Subtract(dt0).TotalMilliseconds > 3000)
                {
                    // Handle timeout
                    break;
                }
            };
        }

        private void isp_write(uint reg_address, uint reg_data)
        {
            int byte_sel = (int)(reg_address) & 0x3;
            byte_sel = byte_sel * 8;
            reg_data = reg_data & 0xff;
            reg_data = reg_data << byte_sel;
            SendRegDataARM(0x21, reg_address, reg_data);
        }

        private uint i2c_readreg(uint addr)
        {
            uint I2C_addr_base = 0x80250000;
            return read(I2C_addr_base + addr);
        }

        private uint read(uint reg_address)
        {
            uint val = 0;
            ReadRegDataARM(0x31, reg_address, ref val);
            return val;
        }

        private void I2C_Switch_Slaver()
        {
            var recdata = ReadRegister(0x31, 0x8050004e).ToString("X2");
            ulong value = Convert.ToUInt32(recdata, 16) & (~0x10U);
            WriteRegister(0x21, 0x8050004e, value);
        }
    }
}
